package compilers.AST.statement;

import compilers.AST.basics.ArrayValueList;
import compilers.AST.basics.Identifier;
import compilers.AST.expression.Expression;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableArrayElement;
import compilers.symbolTable.SymbolTableElement;
import compilers.symbolTable.SymbolTableScope;
import compilers.symbolTable.SymbolTableVariableElement;

public class ArrayDeclarationStatement extends Statement {

	private TypeSpecifier fType;
	private Identifier fIdentifier;
	private boolean fConstant;
	private Expression fLength;
	private ArrayValueList fRValue;
	
	public ArrayDeclarationStatement() {
		fConstant = false;
		fRValue = null;
	}
	
	public ArrayDeclarationStatement(int lineNr) {
		super(lineNr);
		fConstant = false;
		fRValue = null;
	}
	
	public void print(int identLevel) {
		println(identLevel, "ArrayDeclarationStatement");
		fType.print(identLevel+1);
		if (fConstant) {
			println(identLevel+1, "Constant");
		}
		fIdentifier.print(identLevel+1);
		println(identLevel+1, "Length");
		fLength.print(identLevel+2);
		if (fRValue != null) {
			fRValue.print(identLevel+1);
		}
 	}
	
	public void setType(TypeSpecifier type) {
		fType = type;
	}
	
	public void setIdentifier(Identifier ident) {
		fIdentifier = ident;
	}
	
	public void makeConstant() {
		fConstant = true;
	}
	
	public void setLength(Expression length) {
		fLength = length;
	}
	
	public void setRValue(ArrayValueList rValue) {
		fRValue = rValue;
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		fType.checkSemantics(currentScope);
		fLength.checkSemantics(currentScope);
		if ((fLength.getResultType(currentScope) != TypeSpecifier.INTEGER) && (fLength.getResultType(currentScope) != TypeSpecifier.FLOAT)) {
			throw new SemanticalException("The length of the array should be of a numerical type.", fLineNr);
		}
		if (fRValue != null) {
			fRValue.checkSemantics(currentScope);
			SymbolTableElement element = currentScope.getElement(fIdentifier.getValue(), fNodeId);
			if (!(element instanceof SymbolTableArrayElement)) {
				throw new SemanticalException("'" + fIdentifier.getValue() + "' is not an array." , getLine());
			}
			
			TypeSpecifier rValueType = new TypeSpecifier(fRValue.getResultType(currentScope));
			if (element.getType().getType(currentScope) == TypeSpecifier.FLOAT) {
				if ( (rValueType.getType(currentScope) != TypeSpecifier.FLOAT) && (rValueType.getType(currentScope) != TypeSpecifier.INTEGER)) {
					throw new SemanticalException("Assignment of type " + rValueType.getTypeName() + " to variable of type " + element.getType().getTypeName() + " is not allowed.", getLine());
				}
			} else {
				if (fType.getType(currentScope) != rValueType.getType(currentScope)) {
					throw new SemanticalException("Assignment of type '" + rValueType.getTypeName() + "' to array of type '" + fType.getTypeName() + "' is not allowed.", getLine());
				}
			}
		}
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		
		// Calculate start address of the array
		SymbolTableArrayElement element = (SymbolTableArrayElement) currentScope.getElement(fIdentifier.getValue(), fNodeId);
		currentScope.calcDynamicStartAddress(pGen, fNodeId);
		pGen.conv('i', 'a');
		pGen.str('a', currentScope.getDepth(), element.getMemoryAddress());
		
		// Calculate length of the array
		if (fLength.isConstant(currentScope)) {
			pGen.load('i', fLength.calculateValue(currentScope).valueToString());
		} else {
			fLength.generatePcode(pGen, currentScope);
		}
		pGen.str('i', currentScope.getDepth(), element.getMemoryAddress() + 1);
		
		// Assign values if Rvalue is set
		if (fRValue != null) {
			fRValue.generatePcode(pGen, currentScope, element, false);
			
		// Assign 0 values otherwise
		} else {
			String label = "arrayInit_" + fNodeId;
			pGen.load('i', "0");
			pGen.storeAtAddress('i', 0);
			pGen.beginLabel(label);
			pGen.loadFromAddress('i', 0);
			pGen.lod('i', currentScope.getDepth(), element.getMemoryAddress() + 1);
			pGen.less('i');
			pGen.conditionalJump("end_" + label);
			if (element.getType().getType(currentScope) == TypeSpecifier.FLOAT) {
				pGen.load('r', "0.0");
			} else {
				pGen.load(element.getType().getPType(currentScope), "0");
			}
			pGen.loadFromAddress('i', 0);
			pGen.inc('i', 1);
			pGen.storeAtAddress('i', 0);
			pGen.unconditionalJump("begin_" + label);
			pGen.endLabel(label);
		}
	}
}
